package application.java2D;

import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.Arrays;

import javax.swing.JFrame;
import javax.swing.JPanel;

import application.Application;

public abstract class J2DApplication extends Application
{
    public Canvas canvas;
    private BufferedImage buffer;
    private Graphics2D bufferGraphics;
    public int[] pixels;
    public boolean initialized, contained, clearScreenOnRedraw, threadOptimization;
    private int FPS, targetFPS, backgroundColor;
    private double secondsPerUpdate, sleepThreshold;
    
    public J2DApplication()
    {
	super();
    }
    public J2DApplication(int w, int h)
    {
	super(w, h);
    }
    @Override
    protected void initializeApplication() 
    {
	if(initialized)
	    return;
	this.canvas = new Canvas();
	this.buffer = new BufferedImage(this.size.width, this.size.height, BufferedImage.TYPE_INT_ARGB);
	this.bufferGraphics = (Graphics2D)buffer.getGraphics();
	this.pixels = ((DataBufferInt) buffer.getRaster().getDataBuffer()).getData();
	this.setBackgroundColor(0xFF000000);
	this.setClearScreenOnRedraw(true);
	this.targetFPS = Math.min(getScreenRefreshRate(), 60);
	initialized = true;
    }
    @Override
    protected void run() 
    {
		if(this.running)
		    return;
		init();
		running = true;
		long previousLoopTime = System.nanoTime();
        long previousUPSTime = 0;
        int frameCount = 0;
        setTargetFPS(targetFPS);
        while(running)
        {
            long currentTime = System.nanoTime();
            long updateLength = currentTime-previousLoopTime;
            previousLoopTime = currentTime;
            double delta = updateLength / (double)secondsPerUpdate;
            previousUPSTime += updateLength;
            frameCount++;
            if(previousUPSTime >= 1000000000)
            {
                FPS = frameCount;
                //System.out.println("FPS: "+FPS);
                frameCount = 0;
                previousUPSTime = 0;
            }
            update(delta);
            redraw();
            while ( System.nanoTime() - previousLoopTime < secondsPerUpdate)
            {
        	Thread.yield();
        	if(threadOptimization)
        	    while (System.nanoTime()-previousLoopTime<sleepThreshold)
        		try {Thread.sleep(1);} catch(Exception e) {}
            }
        }
    }
    public void setTargetFPS(int newFPS)
    {
        this.targetFPS = newFPS;
        secondsPerUpdate = 1000000000 / targetFPS;
        sleepThreshold = secondsPerUpdate/2.0;
    }
    public void setThreadOptimization(boolean o)
    {
        threadOptimization = o;
    }
    public int getFPS()
    {
	return FPS;
    }
    public Graphics2D getGraphics()
    {
	return bufferGraphics;
    }
    @Override
    protected void redraw() 
    {
	if(this.clearScreenOnRedraw)
	    clearScreen();
	BufferStrategy bs = canvas.getBufferStrategy();
	if(bs != null)
	{
	    Graphics g = bs.getDrawGraphics();
	    render();
	    g.drawImage(buffer, 0, 0, canvas.getWidth(), canvas.getHeight(), null);
	    g.dispose();
	    bs.show();
	}
	else
	{
	    canvas.createBufferStrategy(2);
	    return;
	}
    }
    public void setClearScreenOnRedraw(boolean c)
    {
	this.clearScreenOnRedraw = c;
    }
    public void clearScreen()
    {
	Arrays.fill(this.pixels, this.backgroundColor);
    }
    public void setBackgroundColor(int c)
    {
	System.out.println(c);
	this.backgroundColor = c;
    }
    public void setAA(boolean aa)
    {
	bufferGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, aa?RenderingHints.VALUE_ANTIALIAS_ON:RenderingHints.VALUE_ANTIALIAS_OFF);
    }
    public void setDithering(boolean dither)
    {
	bufferGraphics.setRenderingHint(RenderingHints.KEY_DITHERING, dither?RenderingHints.VALUE_DITHER_ENABLE:RenderingHints.VALUE_DITHER_DISABLE);
    }
    public enum Interpolation
    {
	NEAREST_NEIGHBOR,
	BICUBIC,
	BILINEAR;
    }
    public void setInterpolation(Interpolation inter)
    {
	switch(inter)
	{
	case NEAREST_NEIGHBOR:
    	    bufferGraphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
    	    break;
    	case BILINEAR:
    	    bufferGraphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    	    break;
    	case BICUBIC:
    	    bufferGraphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    	    break;
    	default:
    	    break;
	}
    }
    public void setHighQuality(boolean high)
    {
	bufferGraphics.setRenderingHint(RenderingHints.KEY_RENDERING, high?RenderingHints.VALUE_RENDER_QUALITY:RenderingHints.VALUE_RENDER_SPEED);
    }
    @Override
    public void start()
    {
	if(running)
            return;
	if(!contained)
	{
	    this.startAsWindowedApplication();
	    return;
	}
	new AppThread(this).run();
    }
    public void startAsWindowedApplication()
    {
        if(running||contained)
            return;
        if(!initialized)
            initializeApplication();
        new ApplicationFrame(this);
        contained = true;
        this.start();
    }
    private class ApplicationFrame extends JFrame
    {
        public ApplicationFrame(J2DApplication app)
        {
            this.setContentPane(new JPanel(new BorderLayout()));
            this.add(app.canvas, BorderLayout.CENTER);
            this.setSize(app.size.width, app.size.height);
            this.setLocationRelativeTo(null);
            this.setResizable(false);
            this.setVisible(true);
            this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        }
        private static final long serialVersionUID = 1L;
    }
    public void startAsApplet()
    {
        if(running||contained)
            return;
        if(!initialized)
            initializeApplication();
        new ApplicationApplet(this);
        contained = true;
        this.start();
    }
    private class ApplicationApplet extends Applet
    {
    	public ApplicationApplet(J2DApplication app)
    	{
    	    this.setLayout(new BorderLayout());
    	    this.add(app.canvas, BorderLayout.CENTER);
    	}
    	private static final long serialVersionUID = 1L;
    }
    public void startInApplet(Applet app)
    {
        app.removeAll();
        app.setLayout(new BorderLayout());
        app.add(this.canvas, BorderLayout.CENTER);
        this.start();
    }
}
